package com.yuzhihao.writejvm.attributes;

import com.yuzhihao.writejvm.clazz.ClassInfo;
import com.yuzhihao.writejvm.constant.ConstantUtf8Info;
import com.yuzhihao.writejvm.format.PrintFormat;
import com.yuzhihao.writejvm.read.Parse;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.log4j.Log4j2;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * The LocalVariableTable attribute is an optional variable-length attribute in the attributes table of a Code attribute (§4.7.3). It may be used by debuggers to determine the value of a given local variable during the execution of a method.
 * <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.13">LocalVariableTableAttribute</a>
 *
 * @author yzh
 * @since 2021年12月11日11:56:04
 */
@Data
@Log4j2
@EqualsAndHashCode(callSuper = true)
public class LocalVariableTableAttribute extends Attributes {

    private int local_variable_table_length_u2 = 2;

    private int local_variable_table_length;

    private List<LocalVariable> variables = new ArrayList<>();

    public LocalVariableTableAttribute(String name){
        super();
        super.setAttribute_name(name);
    }

    @Override
    public void parse(DataInputStream dis, ClassInfo ci) {
        this.setCi(ci);
        try {
            super.attribute_length = Parse.byteToInt(super.attribute_length_u4, dis);
            this.local_variable_table_length = Parse.byteToInt(this.local_variable_table_length_u2, dis);

            int i = 0;
            while (i < this.local_variable_table_length){
                LocalVariable line = new LocalVariable();
                line.parse(dis,ci);
                this.variables.add(line);
                i++;
            }
        } catch (IOException e) {
            log.error(e);
        }
    }

    @Override
    public String print(String print) {
        StringBuilder builder = new StringBuilder();
        builder.append(print).append("LocalVariableTable").append(PrintFormat.COLON).append(System.lineSeparator());

        String format = print + PrintFormat.EMPTY;
        String start = "Start  ";
        String length = "Length  ";
        String slot = "Slot  ";
        String name = "Name     ";
        String signature = "Signature  ";

        builder.append(format).append(start).append(length).append(slot).append(name).append(signature).append(System.lineSeparator());
        for (LocalVariable variable : this.variables) {
            builder.append(format);
            builder.append(stable(String.valueOf(variable.getStart_pc()),start));
            builder.append(stable(String.valueOf(variable.getLength()),length));
            builder.append(stable(String.valueOf(variable.getIndex()),slot));
            builder.append(stable(variable.getName(),name));
            builder.append(stable(variable.getDesc(),signature));
            builder.append(System.lineSeparator());
        }

        return builder.toString();
    }

    @Data
    @Log4j2
    public static class LocalVariable implements Parse{

        private int start_pc_u2 = 2;
        private int start_pc;

        private int length_u2 = 2;
        private int length;

        private int name_index_u2 = 2;
        private int name_index;

        private int descriptor_index_u2 = 2;
        private int descriptor_index;

        private int index_u2 = 2;
        private int index;

        private String name;

        private String desc;

        private ClassInfo ci;
        @Override
        public void parse(DataInputStream dis, ClassInfo ci) {
            this.setCi(ci);
            try {
                this.start_pc = Parse.byteToInt(this.start_pc_u2, dis);
                this.length = Parse.byteToInt(this.length_u2, dis);
                this.name_index = Parse.byteToInt(this.name_index_u2, dis);
                this.descriptor_index = Parse.byteToInt(this.descriptor_index_u2, dis);
                this.index = Parse.byteToInt(this.index_u2, dis);
            } catch (IOException e) {
                log.error(e);
            }
        }

        public String getName() {
            if(Objects.nonNull(this.name)){
                return this.name;
            }

            ConstantUtf8Info cui = (ConstantUtf8Info)this.ci.getCp_info().getPools().get(String.valueOf(this.name_index));
            this.name = cui.getConstant();

            return name;
        }

        public String getDesc() {
            if(Objects.nonNull(this.desc)){
                return this.desc;
            }

            ConstantUtf8Info cui = (ConstantUtf8Info)this.ci.getCp_info().getPools().get(String.valueOf(this.descriptor_index));
            this.desc = cui.getConstant();

            return desc;
        }
    }

}
